﻿namespace SharpTalk.Core.Diagnostic
{
    using System;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Threading;

    public sealed class CustomTraceListener : TraceListener
    {
        private readonly string _filename;
        private StreamWriter _writer;
        private ReaderWriterLockSlim _lock;
        private bool _disposed;
        private const int LOCK_TIMEOUT = -1; // Wait indefinitely
        private const string HEADER_FORMAT = "{0:dd.MM.yyyy HH:mm:ss.ffff} ({2})({3,2}) {4,-24} {1,-12}: ";
        private const string FILE_RENAME_TEMPLATE = "{0}_{1:yyyyMMdd_HHmmss}_{2:yyyyMMdd_HHmmss}{3}";
        
        /// <summary>
        /// Constructeur
        /// </summary>
        /// <param name="filename">chemin d'accès au fichier de trace</param>
        public CustomTraceListener(string filename)
        {
            if(string.IsNullOrEmpty(filename))throw new ArgumentNullException(filename);
            _filename = filename;

            _lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
            _lock.EnterWriteLock();

            try
            {
                _writer = CreateWriter(_filename);
            }
            catch
            {
            }
            finally
            {
                if(_lock.IsWriteLockHeld)
                    _lock.ExitWriteLock();
            }
            
        }

        /// <summary>
        /// obtient une valeur qui d�finit si la classe est ThreadSafe. retourne toujour False
        /// </summary>
        public override bool IsThreadSafe
        {
            get
            {
                return true;
            }
        }
        /// <summary>
        /// Ferme le fichier
        /// </summary>
        public override void Close()
        {
            if (_writer == null) return;

            _lock.TryEnterWriteLock(LOCK_TIMEOUT);
            _writer.Close();
            _writer.Dispose();
            _writer = null;
            _lock.ExitWriteLock();
        }
        /// <summary>
        /// Vide le tampon et provoque l'ecriture des données
        /// </summary>
        public override void Flush()
        {
            if (_writer == null) return;
            _lock.TryEnterWriteLock(LOCK_TIMEOUT);
            _writer.Flush();
            _lock.ExitWriteLock();
        }
        /// <summary>
        /// Libère les ressources
        /// </summary>
        /// <param name="disposing"></param>
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            this.Close();

            if(_lock!=null)
                _lock.Dispose();
            
            _lock = null;

            _disposed = true;
            GC.SuppressFinalize(this);
        }
        /// <summary>
        /// Ecrit un message
        /// </summary>
        /// <param name="message">message � ecrire</param>
        public override void Write(string message)
        {
            if (_writer==null) return;
            try
            {
                _writer.Write(message);
            }
            catch
            {
            }
        }
        /// <summary>
        /// Ecrit un message sur une ligne
        /// </summary>
        /// <param name="message">message à écrire</param>
        public override void WriteLine(string message)
        {
            this.Write(message.Replace(Environment.NewLine, String.Empty) + Environment.NewLine);
        }
        /// <summary>
        /// Trace un evenement
        /// </summary>
        /// <param name="eventCache"></param>
        /// <param name="source"></param>
        /// <param name="eventType"></param>
        /// <param name="id"></param>
        /// <param name="format"></param>
        /// <param name="args"></param>
        public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args)
        {
            if(_disposed) return;
            _lock.TryEnterWriteLock(LOCK_TIMEOUT);
            try
            {
                this.WriteHeader(eventCache, eventType, source);
                this.WriteLine(args != null ? string.Format(CultureInfo.InvariantCulture, format, args) : format);
            }
            catch 
            {
            }
            finally
            {
                if (_lock.IsWriteLockHeld)
                    _lock.ExitWriteLock();    
            }
        }
        /// <summary>
        /// Trace un evenement
        /// </summary>
        /// <param name="eventCache"></param>
        /// <param name="source"></param>
        /// <param name="eventType"></param>
        /// <param name="id"></param>
        public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id)
        {
            this.TraceEvent(eventCache, source, eventType, id, null, null);
        }
        /// <summary>
        /// Trace un evenement
        /// </summary>
        /// <param name="eventCache"></param>
        /// <param name="source"></param>
        /// <param name="eventType"></param>
        /// <param name="id"></param>
        /// <param name="message"></param>
        public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message)
        {
            this.TraceEvent(eventCache, source, eventType, id, message, null);
        }

        /// <summary>
        /// Ecrit l'entete du message a tracer.
        /// </summary>
        /// <param name="eventCache"></param>
        /// <param name="eventType"></param>
        /// <param name="source"></param>
        private void WriteHeader(TraceEventCache eventCache, TraceEventType eventType, string source)
        {
            this.Write(string.Format(CultureInfo.InvariantCulture, HEADER_FORMAT,
                DateTime.Now,
                eventType,
                eventCache.ProcessId,
                eventCache.ThreadId,
                source));
        }

        private static StreamWriter CreateWriter(string filename)
        {
            if (File.Exists(filename))
                try
                {
                    RenameFile(filename);
                }
                catch
                {
                }

            using (var st = File.Create(filename))
                st.Close();

            File.SetCreationTime(filename, DateTime.Now);
            return new StreamWriter(new FileStream(filename, FileMode.Append, FileAccess.Write, FileShare.Read, 512)) { AutoFlush = true };
        }
        /// <summary>
        /// Renome un fichier. 
        /// (ex: mylog.log en mylog_20090730_195438_20090731_142619.log)
        /// La premi�re partie correspond � la date_heure de cr�ation du fichier.
        /// La deuxi�me partie correspond � la date_heure de derni�re �criture.
        /// </summary>
        /// <param name="filename">nom du fichier avec extension</param>
        private static void RenameFile(string filename)
        {
            var finfo = new FileInfo(filename);
            if(finfo.Length == 0)
            {
                File.Delete(filename);
                return;
            }
            
            string dir = finfo.DirectoryName;

            // création du nouveau nom du fichier
            string newFileName = String.Format(FILE_RENAME_TEMPLATE,
                Path.GetFileNameWithoutExtension(finfo.Name),
                finfo.CreationTime,
                finfo.LastWriteTime,
                finfo.Extension);

            finfo.CopyTo(dir + "\\" + newFileName);
        }
    }
}